/* 
 * Copyright (c) 2006, Bob Breznak
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *    * Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * For elements developed by Google Inc.
 * 
 * Copyright 2006 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/*
 * The calendar is based off of the Simple Calendar Widget for GWT. 
 * 
 * Simple Calendar Widget for GWT
 * Copyright (C) 2006 Alexei Sokolov http://gwt.components.googlepages.com/

 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */ 
package com.iisageek.client;

import java.util.ArrayList;
import java.util.Date;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ChangeListenerCollection;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SourcesChangeEvents;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.iisageek.client.dialogs.LoadingDialog;
import com.iisageek.client.widgets.xml.Node;
import com.iisageek.client.widgets.xml.Server;
import com.iisageek.client.widgets.xml.XMLHandler;

/**
 * the dynamic panel that handles most functions under the "Workorders" section
 * 
 * Singleton.
 * 
 * @author Bob Breznak
 *
 */
public class WorkordersPanel extends FlowPanel{
	
	private static WorkordersPanel singleton;	
	private final EventsCalendar cal = new EventsCalendar();
	private static final LoadingDialog loadingDlg = new LoadingDialog();
	
	/**
	 * WorkordersPanel
	 * 
	 * sets the instance of WorkordersPanel to the singleton
	 */
	public WorkordersPanel() {
		singleton = this;
	}
	
	/**
	 * setMode
	 * 
	 * determines what functionality to display
	 * 
	 * @param mode (String) the function to display
	 */
	public void setMode(String mode) {
		clear();
		if(mode.equalsIgnoreCase(Shortcuts.WORKORDERS_TAG + "eventscalendar")) {
			add(cal);
		} else {
			lnl.get().showError();
		}
	}
	
	/**
	 * get
	 * 
	 * Returns the single instance of MembersPanel
	 * 
	 * @return MembersPanel
	 */
	public static WorkordersPanel get() {
		return singleton;
	}

	/**
	 * EventsCalendar
	 * 
	 * the calendar widget and all of its contained processes
	 * 
	 * @author Bob Breznak
	 *
	 */
	public class EventsCalendar extends Composite implements ClickListener, SourcesChangeEvents, XMLHandler {

		private ArrayList events = new ArrayList();
		private final NavBar navbar = new NavBar(this);
		private final DockPanel outer = new DockPanel();
		private final DayViewDialog dayviewDlg = new DayViewDialog();
		private FlexTable table = new FlexTable();
		
		private final Grid grid = new Grid(6, 7) {
			public boolean clearCell(int row, int column) {
				boolean retValue = super.clearCell(row, column);
				     
				Element td = getCellFormatter().getElement(row, column);
				DOM.setInnerHTML(td, "");
				return retValue;
			}
		};
		
		private Date date = new Date();
		private ChangeListenerCollection changeListeners;
		private String[] days = new String[] { "Sunday", "Monday", "Tuesday",
											   "Wednesday", "Thursday", "Friday", "Saturday" };
		private String[] months = new String[] { "January", "February", "March",
												 "April", "May", "June", "July", "August", "September", 
												 "October", "November", "December" };
		
		/**
		 * EventsCalendar
		 * 
		 * generate the events calendar heading and set it's inital values
		 *
		 */
		public EventsCalendar() {
			VerticalPanel complex = new VerticalPanel();
			complex.add(outer);
			complex.add(table);
			initWidget(complex);
			grid.setPixelSize(500, 300);
			grid.setStyleName("table");
			grid.setCellSpacing(0);
			outer.add(navbar, DockPanel.NORTH);
			outer.add(grid, DockPanel.CENTER);
			
			buildCalendar();
			outer.setStyleName("CalendarWidget");
		}
		
		/**
		 * buildCalendar
		 * 
		 * build the calendar, display the loading dialog and send a HTTP request for
		 * all events associated with the month
		 *
		 */
		public void buildCalendar() {
			int year = getYear();
			int month = getMonth()+1;
						
			loadingDlg.show();
			
			Server.requestData(Configure.getServer() + "/db.cgi?rm=EventsCalendar&" +
					                                            "start=" + year + ":" + month + ":01&" +
					                                            "end=" + year + ":" + month + ":31", this);
		}


		/**
		 * receivedData
		 * 
		 * Invoked from the HTTP call in the constructor.
		 * 
		 * @param root (Node) the root XML node returned from the HTTP call  
		 */
		public void receivedData(Node root) {
			String date = "";
			events.clear();
			
			for(int i=0;i<root.getChildren().length;i++) {
				date=root.getChild(i).getChild("setup").getText().substring(0, 10).split("-")[0];
				date+=":"+root.getChild(i).getChild("setup").getText().substring(0, 10).split("-")[1];
				date+=":"+root.getChild(i).getChild("setup").getText().substring(0, 10).split("-")[2];
				
				events.add(date);
			}
			drawCalendar();
			loadingDlg.hide();
		}	
		
		/**
		 * getEvents
		 * 
		 * find the number of events that occur on the selected day
		 * 
		 * @param day (int) the date of the month
		 * @return (int) the number of events that occur on the selected day
		 */
		private int getEvents(int day) {
			if(events.size() < 1) return 0;  
			int count=0;
			for(int i=0;i<events.size();i++) {
				count += ((String) events.get(i)).equals(getYear() + ":" + 
						                                 ((getMonth()+1)<10?"0"+( getMonth()+1 ):(getMonth()+1)+"") + ":" + 
						                                 ((day)<10?"0"+( day ):(day)+""))?1:0;
			}
			
			return count;
		}
		
		/**
		 * drawCalendar
		 * 
		 * go though and setup the calendar. write the days, show events, etc...
		 *
		 */
		private void drawCalendar() {
			int year = getYear();
			int month = getMonth();
			setHeaderText(year, month);
			grid.getRowFormatter().setStyleName(0, "weekheader");
			for (int i = 0; i < days.length; i++) {
				grid.getCellFormatter().setStyleName(0, i, "days");
				grid.setText(0, i, days[i].substring(0, 3));
			}
			
			Date now = new Date();
			int sameDay = now.getDate();
			int today = (now.getMonth() == month && now.getYear()+1900 == year) ? sameDay : 0;
		  
			int firstDay = new Date(year - 1900, month, 1).getDay();
			int numOfDays = getDaysInMonth(year, month);
			int j = 0;
			for (int i = 1; i < 6; i++) {
				for (int k = 0; k < 7; k++, j++) {
					int displayNum = (j - firstDay + 1);
					if (j < firstDay || displayNum > numOfDays) {
						grid.getCellFormatter().setStyleName(i, k, "empty");
						grid.setHTML(i, k, "&nbsp;");
					} else {
						HTML html = new CellHTML(
								"<span>" + String.valueOf(displayNum) + "</span>", 
								displayNum);
						html.addClickListener(this);
						grid.getCellFormatter().setStyleName(i, k, "cell");
							
						if (displayNum == today && getEvents(displayNum) > 0) {
							grid.getCellFormatter().addStyleName(i, k, "event-today");
						}else if (displayNum == today) {
							grid.getCellFormatter().addStyleName(i, k, "today");
						}else if(getEvents(displayNum) > Configure.getHighEventNumber()) {
							grid.getCellFormatter().addStyleName(i, k, "high");
						} else if(getEvents(displayNum) > Configure.getMedEventNumber()) {
							grid.getCellFormatter().addStyleName(i, k, "med");
						} else if(getEvents(displayNum) > Configure.getLowEventNumber()) {
							grid.getCellFormatter().addStyleName(i, k, "low");
						} else if (displayNum == sameDay) {
							grid.getCellFormatter().addStyleName(i, k, "day");
						} 
						grid.setWidget(i, k, html);
					}
		    	}
		  	}
		}
	   
		/**
		 * getDaysInMonth
		 * 
		 * returns the number of days in the month
		 * 
		 * @param year (int) the current year
		 * @param month (int) the current month
		 * @return (int) the number of days in the month
		 */
		private int getDaysInMonth(int year, int month) {
			switch (month) {
	    	case 1:
	    		if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
	    			return 29; // leap year
	    		else
	    			return 28;
	    	case 3:
	    		return 30;
	    	case 5:
	    		return 30;
	    	case 8:
	    		return 30;
	    	case 10:
	    		return 30;
	    	default:
	    		return 31;
			}
		}
		
		/**
		 * onClick
		 * 
		 * fired when a day is selected
		 * 
		 * @param sender (Widget) the CellHTML that was clicked
		 */
		public void onClick(Widget sender) {
			CellHTML cell = (CellHTML)sender;

			setDate(getYear(), getMonth(), cell.getDay());
			
			int left = 248;
			int top = 387;

			dayviewDlg.setPopupPosition(left, top);
			dayviewDlg.setDlgDate(getDate());
			dayviewDlg.show();
			
		}

		/** 
		 * addChangeListener
		 * 
		 * add a change listener for any events changes to the grid
		 * 
		 * @param listener(ChangeListener) the class that will listen for any changes
		 */
		public void addChangeListener(ChangeListener listener) {
			if (changeListeners == null) {
				changeListeners = new ChangeListenerCollection();
				changeListeners.add(listener);
			}
			
		}
		
		/**
		 * removeChangeListener
		 * 
		 * remove the class from being contacted when a change occurs
		 * 
		 * @param listener (ChangeListener)
		 */
		public void removeChangeListener(ChangeListener listener) {
			if (changeListeners != null) {
				changeListeners.remove(listener);
			}
		}
		
		/**
		 * prevMonth
		 * 
		 * return the number of the month before the current
		 */
		public void prevMonth() {
			int month = getMonth() - 1;
			if (month < 0) {
				setDate(getYear() - 1, 11, getDay());
			} else {
				setMonth(month);
			}
			buildCalendar();
			
		}
		
		/**
		 * nextMonth
		 * 
		 * return the number of the month after the current
		 */
		public void nextMonth() {
			int month = getMonth() + 1;
			if (month > 11) {
				setDate(getYear() + 1, 0, getDay());
			} else {
				setMonth(month);
			}
			buildCalendar();
		}

		/**
		 * prevYear
		 * 
		 * return the number of the year before the current
		 */
		public void prevYear() {
			setYear(getYear() - 1);
			buildCalendar();
		}

		/**
		 * nextYear
		 * 
		 * return the number of the year after the current
		 */
		public void nextYear() {
			setYear(getYear() + 1);
			buildCalendar();
		}
		
		/**
		 * setHeaderText
		 * 
		 * set the text that is at the top of the navigation
		 * 
		 * @param year (int) the year of the calendar
		 * @param month (int) the month of the calendar
		 */
		protected void setHeaderText(int year, int month) {
			navbar.title.setText(months[month] + ", " + year);
		}

		/**
		 * setDate
		 * 
		 * set the current date of the calendar
		 * @param year (int) the year
		 * @param month (int) the month
		 * @param day (int) the day
		 */
		private void setDate(int year, int month, int day) {
			date = new Date(year - 1900, month, day);
		}
		
		/**
		 * setYear
		 * 
		 * set the current year of the calendar
		 * 
		 * @param year (int) the year to be set
		 */
		private void setYear(int year) {
			date.setYear(year - 1900);
		}

		/**
		 * setMonth
		 * 
		 * set the current month
		 * 
		 * @param month (int) the month to be set
		 */
		private void setMonth(int month) {
			date.setMonth(month);
		}

		/** 
		 * getYear
		 * 
		 * return the current year
		 * @return the current year
		 */
		public int getYear() {
				return 1900 + date.getYear();
		}

		/**
		 * getMonth
		 * 
		 * return the current month
		 * 
		 * @return the current month - 1
		 */
		public int getMonth() {
			return date.getMonth();
		}

		/**
		 * getDay
		 * 
		 * returns the current day
		 * 
		 * @return (int) the current day
		 */
		public int getDay() {
			return date.getDate();
		}
	   
		/**
		 * getDate
		 * 
		 * returns the current date
		 * @return (Date) the current date
		 */
		public Date getDate() {
				return date;
		}
		
		/**
		 * NavBar
		 * 
		 * the bar at the top of the calendar
		 * 
		 * @author Alexei Sokolov
		 * @modified Bob Breznak
		 */
		private class NavBar extends Composite implements ClickListener{
			
			public final DockPanel bar = new DockPanel();
			public final Button prevMonth = new Button("&lt;", this);
			public final Button prevYear = new Button("&lt;&lt;", this);
			public final Button nextYear = new Button("&gt;&gt;", this);
			public final Button nextMonth = new Button("&gt;", this);
			public final HTML title = new HTML();
			private final EventsCalendar calendar;
		
			/**
			 * NavBar
			 * 
			 * initialzes the nav panel and adds the calendar
			 * 
			 * @param calendar (EventsCalendar) the calendar to work with
			 */
		    public NavBar(EventsCalendar calendar) {
		    	this.calendar = calendar;
		
		    	initWidget(bar);
				bar.setStyleName("navbar");
				title.setStyleName("header");
				
				HorizontalPanel prevButtons = new HorizontalPanel();
				prevButtons.add(prevMonth);
				prevButtons.add(prevYear);
				HorizontalPanel nextButtons = new HorizontalPanel();
				nextButtons.add(nextYear);
				nextButtons.add(nextMonth);
				bar.add(prevButtons, DockPanel.WEST);
				bar.setCellHorizontalAlignment(prevButtons, DockPanel.ALIGN_LEFT);
				bar.add(nextButtons, DockPanel.EAST);
				bar.setCellHorizontalAlignment(nextButtons, DockPanel.ALIGN_RIGHT);
				bar.add(title, DockPanel.CENTER);
				bar.setVerticalAlignment(DockPanel.ALIGN_MIDDLE);
				bar.setCellHorizontalAlignment(title, HasAlignment.ALIGN_CENTER);
				bar.setCellVerticalAlignment(title, HasAlignment.ALIGN_MIDDLE);
				bar.setCellWidth(title, "100%");
			}
		    
			/**
			 * onClick
			 * 
			 * fired with a button is clicked
			 * 
			 * @param sender (Widget)
			 */
			public void onClick(Widget sender) {
				if (sender == prevMonth) {
					calendar.prevMonth();
				} else if (sender == prevYear) {
					calendar.prevYear();
				} else if (sender == nextYear) {
					calendar.nextYear();
				} else if (sender == nextMonth) {
					calendar.nextMonth();
				}
			}
		}
		
		/**
		 * CellHTML
		 * 
		 * a HTML panel wrapped with a date for conveinence
		 * 
		 * @author Alexei Sokolov
		 *
		 */
		private class CellHTML extends HTML {
			private int day;
			
			/**
			 * CellHTML
			 * 
			 * sets the html and date
			 * 
			 * @param text
			 * @param day
			 */
			public CellHTML(String text, int day) {
				super(text);
				this.day = day;
			}
	    
			/**
			 * getDay
			 * 
			 * returns the date
			 * @return int
			 */
			public int getDay() {
				return day;
			}
		}
		
		/**
		 * DayViewDialog
		 * 
		 * the popup when a day is selected on the calendar
		 * 
		 * @author Bob Breznak
		 *
		 */
		public class DayViewDialog extends DialogBox implements XMLHandler {

			private Date day;
			private VerticalPanel eventListings = new VerticalPanel();
			private String prettydate;
						
			/**
			 * DayViewDialog
			 * 
			 * Initalize the dialog
			 *
			 */
			public DayViewDialog() {
				day = new Date(0,0,0);
				
				DockPanel outer = new DockPanel();
				outer.setSpacing(4);

				HorizontalPanel buttonPanel = new HorizontalPanel();
				buttonPanel.setHorizontalAlignment(HorizontalPanel.ALIGN_RIGHT);
				buttonPanel.add(new Button("Close", new ClickListener() {
					public void onClick(Widget sender) {
						hide();
					}
				}));
								
				outer.add(buttonPanel, DockPanel.SOUTH);
				outer.add(eventListings, DockPanel.CENTER);

				outer.setSpacing(8);

				super.setWidget(outer);
			}

			/**
			 * setDlgDate
			 * 
			 * set the date of the dialog
			 * @param date (Date) the date to be set to
			 */
			public void setDlgDate(Date date) {
				loadingDlg.show();
				this.day = date;
				setText(day.toString());
				prettydate = ( date.getYear()+1900) + ":" + 
                             ((date.getMonth()+1)<10?"0"+(date.getMonth()+1):(date.getMonth()+1)+"") + ":" + 
                             ((date.getDate()<10)?"0"+date.getDate():date.getDate()+"");
				Server.requestData(Configure.getServer() + "/db.cgi?rm=EventsCalendar&" +
                      "start=" + prettydate + "&" + "end=" + prettydate, this);
				
				//((date.getMonth()+1)<10?"0"+( date.getMonth()+1 ):(date.getMonth()+1)+"") + ":" + ((date.getDay())<10?"0"+( date.getDay() ):(date.getDay())+""))?1:0
				
			}
			
			/**
			 * onKeyDownPreview
			 * 
			 * Exits the AboutDialog when Enter or Escape are pressed
			 * 
			 * @param key the UNICODE of the pressed key
			 * @param modifiers UNICODE of any auxillary keys pressed
			 * @return returns true
			 */
			public boolean onKeyDownPreview(char key, int modifiers) {
				switch (key) {
				case KeyboardListener.KEY_ENTER:
				case KeyboardListener.KEY_ESCAPE:
					hide();
					break;
				}
				return true;
			}

			/**
			 * receivedData
			 * 
			 * Invoked from the HTTP call in the constructor. Loads the configuration or sets internally
			 * a loading error
			 * 
			 * @param root (Node) the root XML node returned from the HTTP call  
			 */
			public void receivedData(Node root) {
				eventListings.clear();
				this.setVisible(false);
				if(root.attributeExists("status")) {
					if(root.getAttribute("status").equals(prettydate + "-to-" + prettydate)) {
						for(int i=0;i<root.getChildren().length;i++) {
							eventListings.add(new Label(root.getChild(i).getAttribute("type_short") + " [" + 
									                    root.getChild(i).getChild("setup").getText().split(" ")[1].substring(0,5) + " - " + 
									                    root.getChild(i).getAttribute("location") + "] " + 
									                    root.getChild(i).getChild("title").getText()));
						}
					}
				}
				loadingDlg.hide();
				this.setVisible(true);
			}
		}
	}
}
